# _*_ coding:utf-8 _*_
#Project:PyLanIm
#Filename:LanImClient.py
#Comment: 支持中文,能监听网关内主机的上下线情况

import wx
import time
import socket
import thread
import sys        
import struct
#import fcntl

port_broadcast = 52524
port_sendmsg = 52525
port_sendfile = 52526

win_width = 600   #主界面宽度
win_height = 550  #主界面高度
bd = 10  #控件与边界空隙宽度

class SimpleClientApp(wx.App): #继承App
    
    def OnInit(self): #类似于构造函数，实例化时调用
        #self.frame = wx.Frame(parent=None, title="LanImClient", size=(win_width, win_height))
        self.frame = wx.Frame(parent=None, title=u"局域网即时通讯客户端", size=(win_width, win_height))
        self.panel = wx.Panel(self.frame, -1)
        
        self.vbox1 = wx.BoxSizer(wx.VERTICAL) #垂直的BoxSizer,是整个主界面左侧的聊天通信部分
        #convText
        self.hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        self.convText = wx.TextCtrl(self.panel, -1, style=wx.TE_MULTILINE | wx.TE_READONLY)
        self.hbox1.Add(self.convText, 1, wx.EXPAND)
        self.vbox1.Add(self.hbox1, 3, wx.EXPAND|wx.ALL, bd)

        #addr
        self.hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        #self.addrLabel = wx.StaticText(self.panel, -1, "TA's IP:", )
        self.addrLabel = wx.StaticText(self.panel, -1, u"对方IP地址:", )
        self.hbox2.Add(self.addrLabel, 0)
        self.addrText = wx.TextCtrl(self.panel, -1, size=(120, 25))
        self.hbox2.Add(self.addrText, 0)
        self.vbox1.Add(self.hbox2, 0, wx.ALIGN_LEFT|wx.TOP|wx.LEFT, bd)
        
        #sendText
        self.hbox3 = wx.BoxSizer(wx.HORIZONTAL)
        self.sendText = wx.TextCtrl(self.panel, -1, style=wx.TE_MULTILINE | wx.TE_PROCESS_ENTER)
        self.hbox3.Add(self.sendText,1, wx.EXPAND)
        self.vbox1.Add(self.hbox3, 1, wx.EXPAND|wx.ALL, bd)
        
        #button
        self.hbox4 = wx.BoxSizer(wx.HORIZONTAL)
        self.sendfileButton = wx.Button(self.panel, label = u"发送文件", size = (80, 25))
        self.hbox4.Add(self.sendfileButton, 0)
        #self.pathText = wx.TextCtrl(self.panel, -1, size=(180,25))
        #self.hbox4.Add(self.pathText, 0)
        self.sendButton = wx.Button(self.panel, label = u"发送",  size=(80, 25))
        self.hbox4.Add(self.sendButton, 0)
        self.exitButton = wx.Button(self.panel, label = u"退出",  size=(80, 25))
        self.hbox4.Add(self.exitButton, 0)
        self.vbox1.Add(self.hbox4, 0, wx.ALIGN_RIGHT|wx.ALL, bd)
        
        self.vbox2 = wx.BoxSizer(wx.VERTICAL) #垂直的Boxsizer,用于显示在线主机
        self.listBox = wx.ListBox(self.panel, -1, style=wx.LB_SINGLE|wx.HSCROLL|wx.LB_ALWAYS_SB|wx.LB_SORT)
        self.vbox2.Add(self.listBox, 1, wx.EXPAND)

        self.box = wx.BoxSizer(wx.HORIZONTAL) #整个主界面的Boxsizer,包括两大部分
        self.box.Add(self.vbox1, 3, wx.EXPAND)#左侧的聊天窗口
        self.box.Add(self.vbox2, 1, wx.EXPAND) #右侧的主机列表
        self.panel.SetSizer(self.box) #将BoxSizer配置应用到panel
        
        self.Bind(wx.EVT_BUTTON, self.OnSendButton, self.sendButton) #sendButton的事件触发
        self.Bind(wx.EVT_BUTTON, self.OnExitButton, self.exitButton) #exitButton的时间触发
        self.Bind(wx.EVT_TEXT_ENTER, self.OnSendButton) #回车事件触发
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxDClick, self.listBox)
        #self.Bind(wx.EVT_BUTTON, self.OnSendFileButton, self.sendfileButton)
        self.sendfileButton.Bind(wx.EVT_BUTTON, self.OnSendFileButton)
        self.addrText.SetFocus() #光标焦点放在IP地址上
        self.frame.Show()
        
        #if system == windows
        #取得本机IP
        self.hostname = socket.gethostname() #本机名
        self.hostipnow = socket.gethostbyname(self.hostname) #本机首选IP
        self.hostiplist = socket.gethostbyname_ex(self.hostname)[2] #本机所有IP
        self.hostipnum = len(self.hostiplist) #所有IP数目
        
        #else system == linux
        
        if self.hostipnum > 1: #如果多于1个IP，就让用户自己选择
            #dialog = wx.SingleChoiceDialog(None, "Choose IP", "Warn", self.hostiplist)
            dialog = wx.SingleChoiceDialog(None, u"请选择合适的IP", u"提示", self.hostiplist)
            if dialog.ShowModal() == wx.ID_OK:
                self.hostipnow = dialog.GetStringSelection().encode('utf8') #因为是在对话框里选的，所以要转成utf8
            dialog.Destroy()

        
        #self.convText.AppendText("\nSystem: Welcome to LanImClient! Your host name is：%s, IP: %s. Input the IP you want to talk with in the text box.\n\n"%(self.hostname, self.hostipnow))
        self.convText.AppendText(u"\n系统提示: 欢迎使用局域网即时通讯客户端! 您的主机名为: %s, IP为: %s. 在下方文本框内输入想要通讯的客户端IP,您就可以开始和TA聊天了!\n\n"%(self.hostname, self.hostipnow))
        self.SendBroadCast_Enter()
        #self.listHost = {self.hostipnow : self.hostname}

        self.listBox.Append(self.hostipnow + "(" +self.hostname+ ")")
        #self.listBox.Append("222.200.56.217(abc)")
        #self.listBox.Append("222.200.22.222(a)")
        #itstr = "222.200.22.223(陈)"
        #self.listBox.Append(itstr)
        #it = self.listBox.FindString(itstr)
        #self.listBox.Delete(it)
        #print it

        return True
    
    def OnExit(self):
        self.SendBroadCast_Exit() #退出前发送广播.不管是点退出、还是按右上角的叉，都会执行这个函数
    
    def OnExitButton(self, event):
        #if wx.YES == wx.MessageBox("Exit?", "Warn", wx.YES_NO|wx.ICON_QUESTION):
        if wx.YES == wx.MessageBox(u"确定要退出客户端?", u"提示", wx.YES_NO|wx.ICON_QUESTION):
            self.Exit()
            
    def OnSendButton(self, event):
        self.SendMessage()


    def OnListBoxDClick(self, event):
        itstr = self.listBox.GetStringSelection().encode('utf8')
        if itstr == "":
            return
        
        spl = itstr.split("(")
        ip = spl[0]
        self.addrText.SetValue(ip.decode('utf8'))
        
    def SendMessage(self):
        msg = self.sendText.GetValue() #msg是unicode编码
        msg_utf8 = msg.encode('utf8') #msg_utf8是utf8编码
        self.sendText.Clear() #发送一次后清空发送文本框
        
        if msg == "": #处理发送消息输入为空
            #wx.MessageBox("Message can't be none!", "Warn", wx.YES_DEFAULT | wx.ICON_QUESTION)
            wx.MessageBox(u"发送内容不能为空, 请重新输入.", u"提示", wx.YES_DEFAULT | wx.ICON_QUESTION)
            return
        self.oppoip = self.addrText.GetValue().encode('utf8')
        if self.oppoip == "": #处理IP为空
            #wx.MessageBox("TA's IP can't be none!", "Warn", wx.YES_DEFAULT | wx.ICON_QUESTION)
            wx.MessageBox(u"对方IP不能为空, 请输入IP地址.", u"提示", wx.YES_DEFAULT | wx.ICON_QUESTION)
            return
        
        if self.oppoip == "127.0.0.1": #如果要与本机通信
            self.oppoip = self.hostipnow
            
        try:            
            sock_clt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建套接字
            sock_clt.settimeout(3)  #设置超时
            sock_clt.connect((self.oppoip, port_sendmsg))  #连接
            sock_clt.send(msg_utf8)  #传输utf8字符串
            sock_clt.close() #销毁套接字
            
            #head = "\nI --> " + self.oppoip + "    " + time.strftime('%Y-%m-%d  %H:%M:%S', time.localtime(time.time()))
            head = u"\n我 --> " + self.oppoip + "    " + time.strftime('%Y-%m-%d  %H:%M:%S', time.localtime(time.time()))
            self.convText.AppendText(head + '\n  ' + msg + '\n')
        except: #超时或连接错误
            #self.convText.AppendText("\nSystem: Can't connect to %s, please check if TA online.\n\n"%self.oppoip)
            self.convText.AppendText(u"\n系统提示: 没有能连接上IP为%s的客户端, 请确认TA是否打开客户端.\n\n"%self.oppoip)

    def RecvMessage(self):
        sock_srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建套接字
        sock_srv.bind((self.hostipnow, port_sendmsg)) #绑定本机IP和端口
        while True:
            sock_srv.listen(10)  #监听
            connection,address = sock_srv.accept() #接收请求

            msg = connection.recv(1024).decode('utf8') #转换以后,msg就是unicode编码的了
            connection.close()
            head = '\n' + address[0] + u" --> 我" + "    " + time.strftime('%Y-%m-%d  %H:%M:%S', time.localtime(time.time()))
            #head = '\n' + address[0] + " --> I" + "    " + time.strftime('%Y-%m-%d  %H:%M:%S', time.localtime(time.time()))
            self.convText.AppendText(head + '\n  ' + msg + '\n')
        sock_srv.close()

    def ListenRecvMessage(self):
        thread.start_new_thread(self.RecvMessage, ()) #创建一个线程来运行RecvMessage()

    def SendBroadCast_Enter(self):
        sock_clt = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock_clt.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        
        msg_enter = ("Enter", self.hostipnow, self.hostname).__str__()
        #需要在调用本函数前得到self.hostipnow和self.hostname
        #print "SendBroadCast_Enter msg_enter: "+ msg_enter
        
        #分割IP地址字符串
        spl = self.hostipnow.split('.')
        ip0 = spl[0]
        ip1 = spl[1]
        ip2 = spl[2]
        ip3 = spl[3]
        broadcastip = ip0 + '.' + ip1 + '.' + ip2 +'.255'  #广播地址，同网关内可以收到
        sock_clt.sendto(msg_enter, (broadcastip, port_broadcast)) #上线离线消息传输使用2524端口
        
        sock_clt.close()
        
        
    def SendBroadCast_Exit(self):
        sock_clt = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock_clt.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        #需要在调用本函数前得到self.hostipnow和self.hostname

        msg_exit = ("Exit", self.hostipnow, self.hostname).__str__()
        #print "SendBroadCast_Exit msg_exit: "+msg_exit
        
        #分割IP地址字符串
        spl = self.hostipnow.split('.')
        ip0 = spl[0]
        ip1 = spl[1]
        ip2 = spl[2]
        ip3 = spl[3]
        broadcastip = ip0 + '.' + ip1 + '.' + ip2 +'.255' #广播地址，同网关内可以收到
        sock_clt.sendto(msg_exit, (broadcastip, port_broadcast))#上线离线消息传输使用2524端口
        
        sock_clt.close()


    def RecvBroadCast(self):
        sock_srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock_srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock_srv.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
        sock_srv.bind((self.hostipnow, port_broadcast))

        while True:
            try:
                #sock_srv.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.hostname), socket.inet_aton('0.0.0.0'))
                #sock_srv.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(self.hostipnow), socket.inet_aton('0.0.0.0'))
                msg_recv,addr_clt = sock_srv.recvfrom(1024)
                #sock_srv.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(self.hostipnow), socket.inet_aton('0.0.0.0'))
            except:
                pass #经常因为这里出错，也许是udp广播不稳定,无视
                #print "RecvBroadCast msg_recv: "+msg_recv
            try:
                #分割消息字符串，得到需要的三个字段内容
                spl = msg_recv.split('\'')
                flag = spl[1]
                ip = spl[3]
                name = spl[5]
                itstr = ip + "(" +name+ ")"
                it = self.listBox.FindString(itstr.decode('utf8'))

                if flag == "Enter": #其他人上线的消息
                    if it == -1:  #列表中没有这个主机
                        self.listBox.Append(itstr.decode('utf8'))
                        #self.listHost[ip] = name
                    msg_res_enter = ("Welcome", self.hostipnow, self.hostname).__str__()
                    sock_srv.sendto(msg_res_enter, (ip, port_broadcast)) #给刚上线的人回应
                    #print "RecvBroadCast :"+ip + "Enter!"

                if flag == "Exit": #其他人离线的消息
                    if it != -1: #在列表中有这个主机
                        self.listBox.Delete(it)
                        #del self.listHost[ip]
                    #print "RecvBroadCast :"+ip +" Exit!"

                if flag == "Welcome": #刚上线的人接收到其他人的回应, 为什么放这里？因为接收消息都是在RecvBroadCast里面
                    if it == -1:
                        #self.listHost[ip] = name
                        self.listBox.Append(itstr.decode('utf8'))
                    #print "RecvBroadCast :"+ip +" response you!"
            except:
                pass
        sock_srv.close()

    def ListenRecvBroadCast(self):
        thread.start_new_thread(self.RecvBroadCast, ()) #创建线程处理接收消息



    def OnSendFileButton(self, event):
        self.oppoip = self.addrText.GetValue().encode('utf8')
        if self.oppoip in[ "" ,  "127.0.0.1", self.hostipnow]:
            wx.MessageBox(u"对方IP不合法, 请重新输入IP地址.", u"提示", wx.YES_DEFAULT | wx.ICON_QUESTION)
            return
        
        self.sendfilepath = ""#'D:\\sendfile.rar'  = ''
        self.sendfilename = "" #'sendfile.rar'# = ''
        
        filedlg = wx.FileDialog(
            self.frame, message=u"选择文件",
            defaultFile="",
            wildcard = u"所有文件 (*.*)|*.*",
            style=wx.OPEN
            )
        if filedlg.ShowModal() != wx.ID_OK:
            filedlg.Destroy()
            return
        self.sendfilepath = filedlg.GetPath().encode('utf8')
        self.sendfilename = filedlg.GetFilename().encode('utf8')
        filedlg.Destroy()
        self.convText.AppendText(u"\n选择了需要发送的文件  "+self.sendfilepath.decode('utf8')+u"  ,正在尝试建立连接...\n")
        
        thread.start_new_thread(self.SendFile, ())
        return
        
    def SendFile(self):
        # self.sendfilepath, self.sendfilename, self.oppoip   utf8
        
        try:
            sock_clt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建套接字
            #sock_clt.settimeout(10)  #设置超时
            sock_clt.connect((self.oppoip, port_sendfile))  #连接
        except:
            self.convText.AppendText(u"\n系统提示: 没有能连接上IP为 %s 的客户端, 请确认TA是否打开客户端.\n\n"%self.oppoip)
            return 
        
        self.convText.AppendText(u"\n成功连接上对方主机,正在等待对方接收...\n")
        sock_clt.send(self.sendfilename)  #传输utf8字符串的文件名
        
        msg = sock_clt.recv(8)
        if msg == 'Y':
            self.convText.AppendText(u"\n对方同意接收文件,开始传输文件...    " +time.strftime('%Y-%m-%d  %H:%M:%S', time.localtime(time.time())) + "\n")
            f = open(self.sendfilepath.decode('utf8'), 'rb')
            while True:
                data = f.read(1024)
                if not data : break
                while len(data)>0:
                    sent = sock_clt.send(data)
                    data = data[sent:]
            f.flush()
            f.close()
            self.convText.AppendText(u"\n文件传输完毕.    "+time.strftime('%Y-%m-%d  %H:%M:%S', time.localtime(time.time()))+"\n")
        elif msg == 'N':
            self.convText.AppendText(u"\n对方不同意接收文件,取消传输.\n")
        else:
            self.convText.AppendText(u"\n没有收到对方的回应,取消传输.\n")
        sock_clt.close()

    
    def RecvFile(self):
        sock_srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建套接字
        sock_srv.bind((self.hostipnow, port_sendfile)) #绑定本机IP和端口
        while True:
            sock_srv.listen(10)  #监听
            connection,address = sock_srv.accept() #接收请求
            
            msg_fileinfo = connection.recv(512) #msg_fileinfo是utf8格式
            
            if wx.YES != wx.MessageBox(u"是否接收来自 "+ address[0] + u" 的文件  " + msg_fileinfo.decode('utf8') + u"  ?", u"提示", wx.YES_NO|wx.ICON_QUESTION): #没有同意接收
                connection.send("N")
                self.convText.AppendText(u"\n已取消接收文件.\n")
                connection.close()
                continue #没有同意接收，就结束这次接收文件
                
            #同意接收,选择要保存的位置
            self.recvfilename = msg_fileinfo
            dirdlg = wx.DirDialog(self.frame, u"选择保存的位置：", style = wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
            if dirdlg.ShowModal() != wx.ID_OK: #选择了位置
                connection.send("N")
                self.convText.AppendText(u"\n已取消接收文件.\n")
                dirdlg.Destroy()
                connection.close()
                continue #没有选择位置，就结束这次接收文件
                
            #==wx.ID_OK 选择了位置，同意接收
            self.recvdir = dirdlg.GetPath().encode('utf8')
            dirdlg.Destroy()
                    
            if self.recvdir[-1] != '\\': #如果路径末尾不是\，那么加上\
                self.recvdir = self.recvdir + '\\'
            self.recvfilepath = self.recvdir  + self.recvfilename

            connection.send("Y")#回复 Y 告诉客户端，可以发送文件了
            self.convText.AppendText(u"\n正在接收文件...    "+time.strftime('%Y-%m-%d  %H:%M:%S', time.localtime(time.time()))+"\n")
                    
            f = open(self.recvfilepath.decode('utf8'), 'wb')
            while True:
                data = connection.recv(1024)
                if not data : break
                f.write(data)
            f.flush()
            f.close()
            self.convText.AppendText(u"\n文件   "+self.recvfilepath.decode('utf8')+u"  接收完成!    "+time.strftime('%Y-%m-%d  %H:%M:%S', time.localtime(time.time()))+"\n")
            
            connection.close()
            
        sock_srv.close()
    
    
    def ListenRecvFile(self):
        thread.start_new_thread(self.RecvFile, ())

if __name__=='__main__':
    app = SimpleClientApp() #实例化一个App
    app.ListenRecvMessage() #打开线程监听
    app.ListenRecvBroadCast()#监听上线广播
    app.ListenRecvFile() #监听文件接收
    app.MainLoop() #消息循环
